/*
 * Copyright (c) 2014. Sensoro Inc.
 * All rights reserved.
 */

package com.sensoro.beacon.kit;

import android.os.Parcel;
import android.os.Parcelable;

import com.sensoro.beacon.kit.BaseSettings.AdvertisingInterval;
import com.sensoro.beacon.kit.BaseSettings.EnergySavingMode;
import com.sensoro.beacon.kit.BaseSettings.SecureBroadcastInterval;
import com.sensoro.beacon.kit.BaseSettings.TransmitPower;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 * The class of the beacon.The class can show all informantion of the beacon.
 */
public class Beacon implements Parcelable, Cloneable {
	/**
	 * The connected status.
	 */
	public static final int CONNECTED = 0;
	/**
	 * The disconnected status.
	 */
	public static final int DISCONNECTED = 1;

    public static final String FV_10 = "1.0";
    public static final String FV_20 = "2.0";
    public static final String FV_21 = "2.1";
    public static final String FV_22 = "2.2";
    public static final String FV_23 = "2.3";
    public static final String FV_30 = "3.0";
    public static final String FV_31 = "3.1";
    public static final String HV_A0 = "A0";
    public static final String HV_B0 = "B0";
    public static final String HV_C0 = "C0";

	String serialNumber; // SN
	Integer major; // major
    Integer minor; // minor
	String proximityUUID; // proximityUuid
	String macAddress; // MAC
	int rssi; // 信号强度
	int batteryLevel;// 剩余电量
	int remainingLifetime;// 剩余时间
	String hardwareModelName;// 硬件版本
	String firmwareVersion;// 固件版本
	Integer temperature;// 温度
	Double light; // 光线照度
	int accelerometerCount; // 加速度计数器
	double accuracy; // 距离
	Proximity proximity; // 接近度
	int measuredPower;// 测量功率
	MovingState movingState; // beacon运动状态
	double runningAverageRssi; // 平均rssi
	BaseSettings baseSettings; // 基础设置
	SensorSettings sensorSettings; // 传感器设置
	SecureBroadcastInterval secureBroadcastInterval; // 安全广播轮换时间
	boolean isIBeaconEnabled; // beacon 是否开启iBeacon功能
	boolean isSecretEnabled; // beacon 是否开启加密功能
	boolean isPasswordEnabled; // beacon 是否开启密码功能
	boolean isEnergySavingEnabled; // beacon 是否开节能模式
    TransmitPower transmitPower;    // beacon 广播功率
    AdvertisingInterval advertisingInterval;    // beacon 广播间隔
    EnergySavingMode energySavingMode;  // beacon 省电模式
    BeaconListener beaconListener;  // beacon 数据更新监听

	@Override
	public int describeContents() {
		return 0;
	}

	@Override
	public void writeToParcel(Parcel out, int flags) {
		out.writeString(serialNumber);
		out.writeSerializable(major);
		out.writeSerializable(minor);
		out.writeString(proximityUUID);
		out.writeString(macAddress);
		out.writeInt(rssi);
		out.writeInt(batteryLevel);
		out.writeInt(remainingLifetime);
		out.writeString(hardwareModelName);
		out.writeString(firmwareVersion);
        out.writeSerializable(temperature);
        out.writeSerializable(light);
        out.writeInt(accelerometerCount);
		out.writeDouble(accuracy);
		out.writeInt(proximity.ordinal());
		out.writeInt(measuredPower);
        out.writeInt(movingState.ordinal());
		out.writeDouble(runningAverageRssi);
		out.writeParcelable(baseSettings, flags);
		out.writeParcelable(sensorSettings, flags);
		out.writeInt(secureBroadcastInterval.ordinal());
		out.writeByte((byte) (isIBeaconEnabled ? 1 : 0));
		out.writeByte((byte) (isSecretEnabled ? 1 : 0));
		out.writeByte((byte) (isPasswordEnabled ? 1 : 0));
		out.writeByte((byte) (isEnergySavingEnabled ? 1 : 0));
		out.writeInt(transmitPower.ordinal());
		out.writeInt(advertisingInterval.ordinal());
		out.writeInt(energySavingMode.ordinal());
		out.writeParcelable(beaconListener,0);
	}

	@Override
	public Beacon clone() throws CloneNotSupportedException {
		Beacon newBeacon = null;
		try {
			newBeacon = (Beacon) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return newBeacon;
	}

    public static final Creator<Beacon> CREATOR = new Creator<Beacon>() {

		@Override
		public Beacon createFromParcel(Parcel parcel) {
			return new Beacon(parcel);
		}

		@Override
		public Beacon[] newArray(int size) {
			return new Beacon[size];
		}
	};

	private Beacon(Parcel in) {
		serialNumber = in.readString();
        if (in != null){
            major = (Integer) in.readSerializable();
        } else {
            major = null;
        }
        if (in != null){
            minor = (Integer) in.readSerializable();
        } else {
            minor = null;
        }
		proximityUUID = in.readString();
		macAddress = in.readString();
		rssi = in.readInt();
		batteryLevel = in.readInt();
		remainingLifetime = in.readInt();
		hardwareModelName = in.readString();
		firmwareVersion = in.readString();
        if (in != null){
            temperature = (Integer) in.readSerializable();
        } else {
            temperature = null;
        }
        if (in != null){
            light = (Double) in.readSerializable();
        } else {
            light = null;
        }
        accelerometerCount = in.readInt();
		accuracy = in.readDouble();
		proximity = Proximity.values()[in.readInt()];
		measuredPower = in.readInt();
		movingState = MovingState.values()[in.readInt()];
		runningAverageRssi = in.readDouble();
		baseSettings = in.readParcelable(BaseSettings.class.getClassLoader());
		sensorSettings = in.readParcelable(SensorSettings.class.getClassLoader());
		secureBroadcastInterval = SecureBroadcastInterval.values()[in.readInt()];
		isIBeaconEnabled = in.readByte() != 0;
		isSecretEnabled = in.readByte() != 0;
		isPasswordEnabled = in.readByte() != 0;
        isEnergySavingEnabled = in.readByte() != 0;
        transmitPower = TransmitPower.values()[in.readInt()];
        advertisingInterval = AdvertisingInterval.values()[in.readInt()];
        energySavingMode = EnergySavingMode.values()[in.readInt()];
        if (in != null){
            beaconListener = in.readParcelable(BeaconListener.class.getClassLoader());
        } else {
            beaconListener = null;
        }
	}

	protected Beacon() {
		serialNumber = null;
		major = null;
		minor = null;
		proximityUUID = null;
		macAddress = null;
		rssi = 0;
		batteryLevel = 0;
		remainingLifetime = 0;
		hardwareModelName = null;
		firmwareVersion = null;
		temperature = null;
        light = null;
        accelerometerCount = 0;
		accuracy = 0;
		proximity = Proximity.PROXIMITY_UNKNOWN;
		measuredPower = 0;
		movingState = MovingState.UNKNOWN;
		runningAverageRssi = 0;
		baseSettings = null;
		sensorSettings = null;
		secureBroadcastInterval = SecureBroadcastInterval.UNKNOWN;
		isIBeaconEnabled = true;
		isSecretEnabled = false;
		isPasswordEnabled = false;
        isEnergySavingEnabled = false;
        transmitPower = TransmitPower.UNKNOWN;
        advertisingInterval = AdvertisingInterval.UNKNOWN;
        energySavingMode = EnergySavingMode.UNKNOWN;
        beaconListener = null;
	}

	/**
	 * Get the SN of the beacon.
	 * 
	 * @return SN.
	 */
	public String getSerialNumber() {
		return serialNumber;
	}

	/**
	 * Get the moving state of the beacon.
	 * 
	 * @return The value of movingState. {@link com.sensoro.beacon.kit.Beacon.MovingState} <br/>
	 */
	public MovingState getMovingState() {
		return movingState;
	}

	/**
	 * Get the rest percent of the battery level.
	 * 
	 * @return The rest battery level.90 means 90%.
	 */
	public int getBatteryLevel() {
		return batteryLevel;
	}

	/**
	 * Get the remaining time of beacon
	 * 
	 * @return remaining time
	 */
	protected int getRemainingLifetime() {
		return remainingLifetime;
	}

	/**
	 * Get the hardware model of the beacon
	 * 
	 * @return Hardware mode(only support A0 beacon and B0 beacon in current)
	 */
	public String getHardwareModelName() {
		return hardwareModelName;
	}

	/**
	 * Get the firmware version of the beacon.
	 * 
	 * @return Firmware version.
	 */
	public String getFirmwareVersion() {
		return firmwareVersion;
	}

	/**
	 * Get the environment temperature of the beacon.
	 * 
	 * @return The value of temperature(unit:centigrade) <br/>
	 *         Null means the beacon has no temperature
	 *         sensor or the temperature sensor is disabled.
	 */
	public Integer getTemperature() {
		return temperature;
	}

	/**
	 * Get the illumination intensity of the beacon.
	 * 
	 * @return The illumination intensity of the beacon (unit:lux) <br/>
	 *         Null means that the beacon has no light
	 *         sensor or the light sensor is disabled.
	 */
	public Double getLight() {
		return light;
	}

	/**
	 * Get the trigger count of the acceleration sensor of beacon.
	 * 
	 * @return The trigger count of acceleration sensor <br/>
	 *         {@link Integer#MAX_VALUE} means the beacon has no accelerator
	 *         sensor or the accelerator sensor is disabled.
	 */
	public int getAccelerometerCount() {
		return accelerometerCount;
	}

	protected double getRunningAverageRssi() {
		return runningAverageRssi;
	}

	/**
	 * Get the accuracy to the beacon.The accuracy is effected by the
	 * environment.
	 * 
	 * @return The accuracy to the beacon.(unit:m)
	 */
	public double getAccuracy() {
		if (accuracy == -1) {
			accuracy = calculateAccuracy(measuredPower,rssi);
		}
		return accuracy;
	}

	/**
	 * Get the proximity of the beacon.
	 * 
	 * @return The proximity level of the beacon
	 *         {@link com.sensoro.beacon.kit.Beacon.Proximity}.
	 */
	public Proximity getProximity() {
		if (proximity == null) {
			proximity = calculateProximity(getAccuracy());
		}
		return proximity;
	}

	/**
	 * Get the rssi of the beacon.
	 * 
	 * @return the value of rssi of beacon
	 */
	public int getRssi() {
		return rssi;
	}

	/**
	 * Get the measured power(mrssi) of the beacon.
	 * 
	 * @return The value of measured power(mrssi) of the beacon.(unit:dbm)
	 */
	public int getMeasuredPower() {
		return measuredPower;
	}

	/**
	 * Get the major of the beacon.
	 * 
	 * @return The major of the beacon.</br>
     *         Null means that the iBeacon function is disabled.
	 */
	public Integer getMajor() {
		return major;
	}

	/**
	 * Get the minor of the beacon.
     *
	 * @return The minor of the beacon.</br>
     *         Null means that the iBeacon function is disabled.
	 */
	public Integer getMinor() {
		return minor;
	}

	/**
	 * Get the mac address of the beacon.
	 * 
	 * @return The mac address of the beacon.
	 */
	public String getMacAddress() {
		return macAddress;
	}

	/**
	 * Get the UUID of the beacon.
	 * 
	 * @return The UUID of the beacon.
	 */
	public String getProximityUUID() {
		return proximityUUID;
	}

	/**
	 * Get the base settings {@link com.sensoro.beacon.kit.BaseSettings} of the
	 * beacon.
	 * 
	 * @return The base settings of the beacon.If the beacon has not been
	 *         connected,return the default invalid value.
	 */
	public BaseSettings getBaseSettings() {
		return baseSettings;
	}

	/**
	 * Get the rotation time of secure broadcast of beacon.
	 * 
	 * @return The rotation time of secure broadcast of beacon.If the beacon has
	 *         not been connected,return the default invalid value.
	 */
	public SecureBroadcastInterval getSecureBroadcastInterval() {
		return secureBroadcastInterval;
	}

    /**
     * Get the advertising interval of the beacon
     *
     * @return value of advertising interval <br/>
     * {@link com.sensoro.beacon.kit.BaseSettings.AdvertisingInterval}
     */
    public AdvertisingInterval getAdvertisingInterval() {
        return advertisingInterval;
    }

    /**
     * Get the transmit power of the beacon.
     *
     * @return value of transmit power <br/>
     * {@link com.sensoro.beacon.kit.BaseSettings.TransmitPower}
     */
    public TransmitPower getTransmitPower() {
        return transmitPower;
    }

	/**
	 * Whether the beacon has enabled iBeacon function or not.
	 * 
	 * @return true or false
	 * 
	 */
	public boolean isIBeaconEnabled() {
		return isIBeaconEnabled;
	}

	/**
	 * Whether the beacon has enabled secret function or not.
	 * 
	 * @return true or false
	 * 
	 */
	public boolean isSecretEnabled() {
		return isSecretEnabled;
	}

	/**
	 * Whether the beacon has enabled password function or not.
	 * 
	 * @return true or false
	 * 
	 */
	public boolean isPasswordEnabled() {
		return isPasswordEnabled;
	}

    /**
	 * Whether the beacon has enabled energy saving function or not.
	 *
	 * @return true or false
	 *
	 */
	public boolean isEnergySavingEnabled() {
		return isEnergySavingEnabled;
	}

	/**
	 * Get the sensor settings of the beacon.
	 * 
	 * @return If the beacon has not bean connected,return the default invalid
	 *         value.
	 */
	public SensorSettings getSensorSettings() {
		return sensorSettings;
	}

	@Override
	public boolean equals(Object that) {
		if (!(that instanceof Beacon)) {
			return false;
		}
		Beacon thatBeacon = (Beacon) that;
		return (thatBeacon.serialNumber.equals(this.serialNumber));
	}

    @Override
    public int hashCode() {
        return serialNumber.hashCode();
    }

    @Override
	public String toString() {
		return "Beacon [major=" + major + ", minor=" + minor + ", proximityUUID=" + proximityUUID + ", serialNumber=" + serialNumber + ", macAddress=" + macAddress + ", rssi=" + rssi + ", batteryLevel=" + batteryLevel + ", remainingLifetime=" + remainingLifetime + ", hardwareModelName=" + hardwareModelName + ", firmwareVersion=" + firmwareVersion + ", temperature=" + temperature + ", light=" + light + ", accelerometerCount=" + accelerometerCount + ", accuracy=" + accuracy + ", proximity="
				+ proximity + ", measuredPower=" + measuredPower + ", movingState=" + movingState + ", runningAverageRssi=" + runningAverageRssi + ", baseSettings=" + baseSettings + ", sensorSettings=" + sensorSettings + ", secureBroadcastInterval=" + secureBroadcastInterval + ", isIBeaconEnabled=" + isIBeaconEnabled + ", isSecretEnabled=" + isSecretEnabled + ", isPasswordEnabled=" + isPasswordEnabled + "]";
	}

	/**
	 * The proximity enum of the beacon.
	 */
	public enum Proximity implements Serializable {
		/**
		 * Immediate.
		 */
		PROXIMITY_IMMEDIATE, // distance < 0.5m
		/**
		 * Near.
		 */
		PROXIMITY_NEAR, // 0.5m < distance < 4m
		/**
		 * Far.
		 */
		PROXIMITY_FAR, // 4m < distance
		/**
		 * Unknown.
		 */
		PROXIMITY_UNKNOWN
	}

	private Proximity calculateProximity(double accuracy) {
		if (accuracy < 0) {
			return Proximity.PROXIMITY_UNKNOWN;
		}
		if (accuracy < 0.5) {
			return Proximity.PROXIMITY_IMMEDIATE;
		}
		if (accuracy <= 4.0) {
			return Proximity.PROXIMITY_NEAR;
		}
		return Proximity.PROXIMITY_FAR;

	}

	private double calculateAccuracy(int txPower, double rssi) {
		if (rssi == 0) {
			return -1.0; // 无法确定距离,返回-1
		}

		double accuracy = 0;
		double ratio = rssi * 1.0 / txPower;
		if (ratio < 1.0) {
			accuracy = Math.pow(ratio, 10);
		} else {
            accuracy = (0.42093) * Math.pow(ratio, 6.9476) + 0.54992;
		}
		return new BigDecimal(Double.toString(accuracy)).setScale(4, BigDecimal.ROUND_HALF_UP).doubleValue();
	}

    public enum MovingState{
        MOVING,STILL,DISABLED,UNKNOWN;

        static MovingState getMovingState(int movingState){
            switch (movingState){
                case 0xff:
                    return DISABLED;
                case 0:
                    return STILL;
                case 1:
                    return MOVING;
                default:
                    return UNKNOWN;
            }
        }
    }
}
